package server;

import interfacePaxos.PaxosRemote;
import interfacePaxos.Utils;

import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Learner {

  /**
   * 
   * acceptor address -> proposal there is no need to persist this data to
   * 
   * disc, since we can just have the proposers propose a new value if the
   * 
   * learner crashes and consequently would like to know what value was chosen
   */

  public boolean[] m_lock = { false, false, false, false, false, false, false,
      false, false, false };
  public String[] m_lock_clients = new String[10];

  private Map<Integer, Proposal> acceptedProposals = new HashMap<Integer, Proposal>();

  private Map<String, Integer> proposalAccepted = new HashMap<String, Integer>();

  private List<PaxosRemote> services;



  /**
   * 
   * Contractor.
   * 
   * 
   * 
   * @param node
   * 
   *          a reference to the node acting this role
   * 
   * @param services
   * 
   *          services provided to this role
   */

  public Learner(int pxId, List<PaxosRemote> services) {


    this.services = services;

  }

  /**
   * 
   * Return a Map from Proposal -> # of nodes that have accepted it. Note that
   * 
   * we only track the latest accepted proposal from each node. This means
   * 
   * that even if a value has been chosen, there are pathological cases where
   * 
   * a learner will never learn the value; the acceptors may be constantly
   * 
   * accepting new proposal /numbers/ such that the learner never sees that a
   * 
   * majority of nodes has accepted any particular proposal.
   */

  private Map<Proposal, Integer> getProposalCounts() {

    Map<Proposal, Integer> counts = new HashMap<Proposal, Integer>();

    for (Proposal proposal : acceptedProposals.values()) {

      if (counts.containsKey(proposal))

        counts.put(proposal, counts.get(proposal) + 1);

      else

        counts.put(proposal, 1);

    }

    return counts;

  }

  /**
   * 
   * Return the Proposal that has been chosen by a majority of acceptors, else
   * 
   * null if no such proposal exists
   */

  @SuppressWarnings({ "unchecked", "unused" })
  private Proposal getChosenProposal() throws RemoteException {

    Map<Proposal, Integer> counts = getProposalCounts();

    for (Map.Entry<Proposal, Integer> pair : counts.entrySet()) {

      if (Utils.isMajorityOf(pair.getValue(),

      ((Map<Integer, Proposal>) ((PaxosRemote) services).getProposalNumber())
          .size())) {

        return pair.getKey();

      }

    }

    return null; // no proposal has been chosen yet

  }

  public Proposal getProposalNumber() {

    int result = 0;
    for (int n : acceptedProposals.keySet()) {

      if (n > result) {

        result = n;

      }

    }
    Proposal p = acceptedProposals.get(result);
    if (p != null) {
      return p;
    } else {
      return new Proposal();
    }
  }

  public void receiveAcceptedProposal(int from, Proposal p) {

    Integer p_count = proposalAccepted.get(p.keyString());
    if (p_count == null) {
      p_count = 0;
    }
    proposalAccepted.put(p.keyString(), ++p_count);
    if (p_count >= (services.size() / 2) + 1) {
      this.m_lock[p.getNumLock()] = p.getLock();
      m_lock_clients[p.getNumLock()] = p.getClient();
      // System.out.println(Arrays.toString(m_lock));
      // System.out.println(Arrays.toString(m_lock_clients));
    } else {
      System.out.println("p_count: " + p_count);
    }
  }

  public boolean checkLock(boolean lock, int number) {
    return m_lock[number];
  }

  public String getLockHolder(int number) {
    return m_lock_clients[number];
  }

  /**
   * update lock array
   * 
   * @param statusLock
   * @param numLock
   * @return
   */

  public void updateLock(boolean statusLock, int numLock) {
    if (statusLock) {
      this.m_lock[numLock] = true;
    } else {
      this.m_lock[numLock] = false;
    }
    System.out.println("Lock " + numLock + " is updated to "
        + this.m_lock[numLock]);
  }

}